{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "import sys\n",
    "sys.path.append('../')  # 返回notebook的上一级目录\n",
    "# sys.path.append('E:\\GitHub\\QA-abstract-and-reasoning')  # 效果同上"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.ticker as ticker\n",
    "np.set_printoptions(suppress=True)\n",
    "from utils.saveLoader import *\n",
    "from utils.config import *\n",
    "\n",
    "from gensim.models.word2vec import LineSentence, Word2Vec\n",
    "import tensorflow as tf\n",
    "# from model_layer import seq2seq_model\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2.0.0'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[限制gpu内存增长](https://tensorflow.google.cn/guide/gpu#limiting_gpu_memory_growth)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 Physical GPUs, 1 Logical GPUs\n"
     ]
    }
   ],
   "source": [
    "gpus = tf.config.experimental.list_physical_devices('GPU')\n",
    "if gpus:\n",
    "    try:\n",
    "        # Currently, memory growth needs to be the same across GPUs\n",
    "        for gpu in gpus:\n",
    "            tf.config.experimental.set_memory_growth(gpu, True)\n",
    "        logical_gpus = tf.config.experimental.list_logical_devices('GPU')\n",
    "        print(len(gpus), \"Physical GPUs,\", len(logical_gpus), \"Logical GPUs\")\n",
    "    except RuntimeError as e:\n",
    "        # Memory growth must be set before GPUs have been initialized\n",
    "        print(e)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_x,train_y,test_x = load_train_dataset()  # 数据集\n",
    "vocab,vocab_reversed = load_vocab(VOCAB_PAD)  # vocab\n",
    "embedding_matrix = np.loadtxt(EMBEDDING_MATRIX_PAD)  # 预训练层"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入长度：260\n",
      "输出长度：33\n",
      "词表大小：32566\n"
     ]
    }
   ],
   "source": [
    "# 输入的长度  train_X.shape -> (82871, 261)\n",
    "input_length = train_x.shape[1]\n",
    "# 输出的长度  train_Y.shape -> (82871, 34)\n",
    "output_sequence_length = train_y.shape[1]\n",
    "# 词表大小\n",
    "vocab_size=len(vocab)\n",
    "print(\"输入长度：{}\\n输出长度：{}\\n词表大小：{}\".format(input_length, output_sequence_length, vocab_size))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1 基本参数设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 取部分数据进行训练\n",
    "sample_num=64\n",
    "train_X=train_x[:sample_num]\n",
    "train_Y=train_y[:sample_num]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 训练集的长度\n",
    "BUFFER_SIZE = len(train_X)\n",
    "\n",
    "# 输入的长度\n",
    "max_length_inp=train_X.shape[1]\n",
    "# 输出的长度\n",
    "max_length_targ=train_Y.shape[1]\n",
    "\n",
    "BATCH_SIZE = 8\n",
    "\n",
    "# 训练一轮需要迭代多少步\n",
    "steps_per_epoch = len(train_X)//BATCH_SIZE\n",
    "\n",
    "# 词向量维度\n",
    "embedding_dim = 300\n",
    "\n",
    "# 隐藏层单元数\n",
    "units = 32\n",
    "\n",
    "# 词表大小\n",
    "vocab_size = len(vocab)\n",
    "\n",
    "# 构建训练集\n",
    "dataset = tf.data.Dataset.from_tensor_slices((train_X, train_Y)).shuffle(BUFFER_SIZE)\n",
    "dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2 构建Encoder"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "返回值具体是什么可以查看[RNN文档](https://tensorflow.google.cn/api_docs/python/tf/keras/layers/RNN)\n",
    "Output shape:\n",
    "\n",
    "- If return_state: a list of tensors. The first tensor is the output. The remaining tensors are the last states, each with shape (batch_size, state_size), where state_size could be a high dimension tensor shape.\n",
    "\n",
    "\n",
    "- If return_sequences: N-D tensor with shape (batch_size, timesteps, output_size), where output_size could be a high dimension tensor shape, or (timesteps, batch_size, output_size) when time_major is True.\n",
    "\n",
    "\n",
    "- Else, N-D tensor with shape (batch_size, output_size), where output_size could be a high dimension tensor shape.\n",
    "\n",
    "[这篇文章](https://blog.csdn.net/u011327333/article/details/78501054)分析了`return_state` 和 `return_sequences`取True 或 False 的四种情况\n",
    "\n",
    "[GRU官方文档](https://tensorflow.google.cn/api_docs/python/tf/keras/layers/GRU#class_gru)\n",
    "\n",
    "<img src=\"./picture/encoder.png\" />"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Encoder(tf.keras.Model):\n",
    "    def __init__(self, vocab_size, embedding_dim ,embedding_matrix , enc_units, batch_sz):\n",
    "        super(Encoder, self).__init__()\n",
    "        self.batch_sz = batch_sz\n",
    "        self.enc_units = enc_units # whats this\n",
    "        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim,weights=[embedding_matrix],trainable=False)\n",
    "        self.gru = tf.keras.layers.GRU(self.enc_units,\n",
    "                                       return_sequences=True,\n",
    "                                       return_state=True,\n",
    "                                       recurrent_initializer='glorot_uniform')\n",
    "\n",
    "    def call(self, x, hidden):\n",
    "        x = self.embedding(x)\n",
    "        # print(\"after embedding\",x.shape)\n",
    "        output, state = self.gru(x, initial_state = hidden)\n",
    "        return output, state\n",
    "\n",
    "    def initialize_hidden_state(self):\n",
    "        # (一批数据大小, 隐层的维数)\n",
    "        return tf.zeros((self.batch_sz, self.enc_units))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(32566, 300, 32, 8)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vocab_size, embedding_dim, units, BATCH_SIZE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(32566, 300)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embedding_matrix.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "encoder = Encoder(vocab_size, embedding_dim,embedding_matrix, units, BATCH_SIZE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(8, 260)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=13, shape=(8, 260), dtype=int32, numpy=\n",
       "array([[32562,   403,   985, ..., 32565, 32565, 32565],\n",
       "       [32562,   816, 26474, ..., 32565, 32565, 32565],\n",
       "       [32562,  1445,    81, ...,    31,     2, 32564],\n",
       "       ...,\n",
       "       [32562,   920,    30, ..., 32565, 32565, 32565],\n",
       "       [32562,  2026,   278, ..., 32565, 32565, 32565],\n",
       "       [32562, 32563,  1033, ..., 32565, 32565, 32565]])>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = tf.cast(train_x[:BATCH_SIZE], dtype=tf.int32)\n",
    "print(x.shape)\n",
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "TensorShape([8, 32])"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sample_hidden = encoder.initialize_hidden_state()\n",
    "sample_hidden.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Encoder output shape: (batch size, sequence length, units) (8, 260, 32)\n",
      "Encoder Hidden state shape: (batch size, units) (8, 32)\n"
     ]
    }
   ],
   "source": [
    "# 感觉这个output的shape很像权重\n",
    "sample_output, sample_hidden = encoder(x, sample_hidden)\n",
    "print ('Encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))\n",
    "print ('Encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"picture/gru.png\" width = \"50%\" height = \"50%\" />"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(encoder.weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3 构建Attention"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BahdanauAttention(tf.keras.layers.Layer):\n",
    "    def __init__(self, units):\n",
    "        super(BahdanauAttention, self).__init__()\n",
    "        self.W1 = tf.keras.layers.Dense(units)\n",
    "        self.W2 = tf.keras.layers.Dense(units)\n",
    "        self.V = tf.keras.layers.Dense(1)\n",
    "\n",
    "    def call(self, query, values):\n",
    "        \n",
    "        # query为上次的GRU隐藏层\n",
    "        # values为编码器的编码结果enc_output\n",
    "        # 在seq2seq模型中，St是后面的query向量，而编码过程的隐藏状态hi是values。\n",
    "        hidden_with_time_axis = tf.expand_dims(query, 1)\n",
    "\n",
    "        \n",
    "        # 计算注意力权重值\n",
    "        score = self.V(tf.nn.tanh(\n",
    "            self.W1(values) + self.W2(hidden_with_time_axis)))\n",
    "\n",
    "        # attention_weights shape == (batch_size, max_length, 1)\n",
    "        attention_weights = tf.nn.softmax(score, axis=1)\n",
    "        \n",
    "        # # 使用注意力权重*编码器输出作为返回值，将来会作为解码器的输入\n",
    "        # context_vector shape after sum == (batch_size, hidden_size)\n",
    "        context_vector = attention_weights * values\n",
    "        context_vector = tf.reduce_sum(context_vector, axis=1)\n",
    "\n",
    "        return context_vector, attention_weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "context_vector shape: (batch size, units) (8, 32)\n",
      "Attention weights shape: (batch_size, sequence_length, 1) (8, 260, 1)\n"
     ]
    }
   ],
   "source": [
    "attention_layer = BahdanauAttention(10)\n",
    "context_vector, attention_weights = attention_layer(sample_hidden, sample_output)\n",
    "\n",
    "print(\"context_vector shape: (batch size, units) {}\".format(context_vector.shape))\n",
    "print(\"Attention weights shape: (batch_size, sequence_length, 1) {}\".format(attention_weights.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个样本是260行1024列的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "输出是260行10列的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4 构建Decoder\n",
    "\n",
    "<img src=\"./picture/decoder.png\" />\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Decoder(tf.keras.Model):\n",
    "    def __init__(self, vocab_size, embedding_dim,embedding_matrix, dec_units, batch_sz):\n",
    "        super(Decoder, self).__init__()\n",
    "        self.batch_sz = batch_sz\n",
    "        self.dec_units = dec_units\n",
    "        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim,weights=[embedding_matrix],trainable=False)\n",
    "        self.gru = tf.keras.layers.GRU(self.dec_units,\n",
    "                                       return_sequences=True,\n",
    "                                       return_state=True,\n",
    "                                       recurrent_initializer='glorot_uniform')\n",
    "        # 之前好像没加softmax\n",
    "        self.fc = tf.keras.layers.Dense(vocab_size, activation=\"softmax\")  # 为了softmax层数要保持一致\n",
    "\n",
    "        # used for attention\n",
    "        self.attention = BahdanauAttention(self.dec_units)\n",
    "\n",
    "    def call(self, x, hidden, enc_output):\n",
    "\n",
    "        context_vector, attention_weights = self.attention(hidden, enc_output)\n",
    "        x = self.embedding(x)\n",
    "        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)\n",
    "        output, state = self.gru(x)\n",
    "        output = tf.reshape(output, (-1, output.shape[2]))\n",
    "        \n",
    "        x = self.fc(output)\n",
    "        \n",
    "        return x, state, attention_weights"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **测试**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Decoder output shape: (batch_size, vocab size) (8, 32566)\n"
     ]
    }
   ],
   "source": [
    "decoder = Decoder(vocab_size, embedding_dim,embedding_matrix, units, BATCH_SIZE)\n",
    "\n",
    "sample_decoder_output, _, _ = decoder(tf.random.uniform((BATCH_SIZE, 1)),\n",
    "                                      sample_hidden, sample_output)\n",
    "\n",
    "print ('Decoder output shape: (batch_size, vocab size) {}'.format(sample_decoder_output.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 优化器和损失函数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[SparseCategoricalCrossentropy](https://tensorflow.google.cn/api_docs/python/tf/keras/losses/SparseCategoricalCrossentropy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = tf.keras.optimizers.Adam()\n",
    "loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')\n",
    "\n",
    "pad_index=vocab['<PAD>']\n",
    "\n",
    "def loss_function(real, pred):\n",
    "    # 相当于把<PAD>给过滤了，词如果是<PAD>那它对应位置的mask为 False\n",
    "    # real = [0, 1, 2] pad_index = 2 --> mask = [True, Ture, False]\n",
    "    # 用于后面不计算<PAD>词的损失\n",
    "    mask = tf.math.logical_not(tf.math.equal(real, pad_index))\n",
    "    # 计算损失\n",
    "    # real = [0, 1, 2] pred = [[.91,.4,.5],[.0, .88, .1],[.3, .3, .94]]\n",
    "    loss_ = loss_object(real, pred)\n",
    "    # bool型转float(与loss_的数据类型一致)\n",
    "    mask = tf.cast(mask, dtype=loss_.dtype)\n",
    "    # 不计算<PAD>词损失值\n",
    "    loss_ *= mask\n",
    "    # 返回损失值之和\n",
    "    return tf.reduce_mean(loss_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 保存点设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint_dir = 'data/checkpoints/training_checkpoints'\n",
    "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n",
    "checkpoint = tf.train.Checkpoint(optimizer=optimizer,\n",
    "                                 encoder=encoder,\n",
    "                                 decoder=decoder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'data/checkpoints/training_checkpoints\\\\ckpt'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint_prefix"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# **训练**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tf.function\n",
    "def train_step(inp, targ, enc_hidden):\n",
    "    loss = 0\n",
    "\n",
    "    with tf.GradientTape() as tape:\n",
    "        # 1. 构建encoder\n",
    "        enc_output, enc_hidden = encoder(inp, enc_hidden)\n",
    "        # 2. 复制\n",
    "        dec_hidden = enc_hidden\n",
    "        # 3. <START> * BATCH_SIZE \n",
    "        # shape: (BATCH_SIZE, 1)\n",
    "        dec_input = tf.expand_dims([vocab['<START>']] * BATCH_SIZE, 1)\n",
    "    \n",
    "        # Teacher forcing - feeding the target as the next input\n",
    "        # 这里跟decoder有点区别，是一个一个输入固定位置的词 dec_input的 shape: (BATCH_SIZE, 1)\n",
    "        # 例如一批数据64句话，第一轮输入<START>,第二轮输入所有句子的第一个词...\n",
    "        for t in range(1, targ.shape[1]):\n",
    "            # targ.shape = (BATCH_SIZE, len_train_Y) 第二个参数是句子长度\n",
    "            # decoder(x, hidden, enc_output)\n",
    "            # predictions用于sotfmax的(BATCH_SIZE, VOCAB_SIZE)向量\n",
    "            predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)\n",
    "            \n",
    "            # 为什么损失值是累加的: 注意这里是targ[:, t]不是targ[:t]\n",
    "            loss += loss_function(targ[:, t], predictions)\n",
    "\n",
    "            # using teacher forcing\n",
    "            # 为下一个输入做准备\n",
    "            dec_input = tf.expand_dims(targ[:, t], 1)  # shape: (BATCH_SIZE, 1)\n",
    "\n",
    "        batch_loss = (loss / int(targ.shape[1]))\n",
    "\n",
    "        variables = encoder.trainable_variables + decoder.trainable_variables\n",
    "\n",
    "        gradients = tape.gradient(loss, variables)\n",
    "\n",
    "        optimizer.apply_gradients(zip(gradients, variables))\n",
    "\n",
    "        return batch_loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1 Batch 0 Loss 4.5264\n",
      "Epoch 1 Batch 1 Loss 5.0381\n",
      "Epoch 1 Batch 2 Loss 4.3296\n",
      "Epoch 1 Batch 3 Loss 5.7466\n",
      "Epoch 1 Batch 4 Loss 4.7232\n",
      "Epoch 1 Batch 5 Loss 5.5104\n",
      "Epoch 1 Batch 6 Loss 4.6051\n",
      "Epoch 1 Batch 7 Loss 5.2742\n",
      "Epoch 1 Loss 4.9692\n",
      "Time taken for 1 epoch 42.60619306564331 sec\n",
      "\n"
     ]
    }
   ],
   "source": [
    "EPOCHS = 1\n",
    "TRAIN = True\n",
    "if TRAIN:\n",
    "    for epoch in range(EPOCHS):\n",
    "        start = time.time()\n",
    "\n",
    "        # 初始化隐藏层\n",
    "        enc_hidden = encoder.initialize_hidden_state()\n",
    "        total_loss = 0\n",
    "\n",
    "        for (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)):\n",
    "            # \n",
    "            batch_loss = train_step(inp, targ, enc_hidden)\n",
    "            total_loss += batch_loss\n",
    "\n",
    "            if batch % 1 == 0:\n",
    "                print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,\n",
    "                                                             batch,\n",
    "                                                             batch_loss.numpy()))\n",
    "        # saving (checkpoint) the model every 2 epochs\n",
    "        if (epoch + 1) % 2 == 0:\n",
    "            checkpoint.save(file_prefix = checkpoint_prefix)\n",
    "\n",
    "        print('Epoch {} Loss {:.4f}'.format(epoch + 1,\n",
    "                                          total_loss / steps_per_epoch))\n",
    "        print('Time taken for 1 epoch {} sec\\n'.format(time.time() - start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 回答\n",
    "* The evaluate function is similar to the training loop, except we don't use <u>*teacher forcing*</u> here. The input to the decoder at each time step is its previous predictions along with the hidden state and the encoder output.\n",
    "* Stop predicting when the model predicts the *end token*.\n",
    "* And store the *attention weights for every time step*.\n",
    "\n",
    "Note: The encoder output is calculated only once for one input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "from preprocess import Preprocess\n",
    "pp = Preprocess()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "sentence='奔驰的方向机重，助力泵，方向机都换了还是一样'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Loading model from cache C:\\Users\\Light\\AppData\\Local\\Temp\\jieba.cache\n",
      "Loading model cost 0.633 seconds.\n",
      "Prefix dict has been built succesfully.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'奔驰 方向机 重 助力 泵 方向机 都 换'"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pp.sentence_proc(sentence)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([32562,   816,   403,   985,   247,   231,   403,     4,    10,\n",
       "       32564])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pp.sentence_proc_eval(sentence,max_length_inp-2,vocab)[0][:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[32562,   403,   985, ..., 32565, 32565, 32565],\n",
       "       [32562,   816, 26474, ..., 32565, 32565, 32565],\n",
       "       [32562,  1445,    81, ...,    31,     2, 32564],\n",
       "       ...,\n",
       "       [32562,  4387, 29078, ..., 32565, 32565, 32565],\n",
       "       [32562,   167,     7, ..., 32565, 32565, 32565],\n",
       "       [32562,  2152,   952, ..., 32565, 32565, 32565]])"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "# from utils.data_loader import preprocess_sentence\n",
    "import matplotlib as mpl\n",
    "mpl.rcParams['font.family'] = 'STSong'  # 显示中文\n",
    "import matplotlib.pyplot as plt\n",
    "# from sklearn.manifold import TSNE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "from icecream import ic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(sentence):\n",
    "    attention_plot = np.zeros((max_length_targ, max_length_inp))\n",
    "\n",
    "    # 需要整合下自己的预处理\n",
    "    # 这里max_length_inp-2为何要-2\n",
    "    # 原本计算得到最大长度为258，经过pad后长度变为260了，如果这里继续用260，input长度会变成262\n",
    "    inputs = pp.sentence_proc_eval(sentence,max_length_inp-2,vocab)\n",
    "\n",
    "    inputs = tf.convert_to_tensor(inputs)\n",
    "\n",
    "    result = ''\n",
    "    \n",
    "    hidden = [tf.zeros((1, units))]  # 这个是不是可以调之前的encoder里的初始化方法\n",
    "    \n",
    "    enc_out, enc_hidden = encoder(inputs, hidden)\n",
    "\n",
    "    dec_hidden = enc_hidden\n",
    "    \n",
    "    dec_input = tf.expand_dims([vocab['<START>']], 0)\n",
    "\n",
    "    for t in range(max_length_targ):\n",
    "        predictions, dec_hidden, attention_weights = decoder(dec_input,\n",
    "                                                             dec_hidden,\n",
    "                                                             enc_out)\n",
    "        # ic(predictions.numpy().sum(axis=1))\n",
    "        # storing the attention weights to plot later on\n",
    "        attention_weights = tf.reshape(attention_weights, (-1, ))\n",
    "        \n",
    "        attention_plot[t] = attention_weights.numpy()\n",
    "        # predictions shape: (1, vocab_size) 和numpy 的argmax()还是有点区别的\n",
    "        predicted_id = tf.argmax(predictions[0]).numpy()\n",
    "\n",
    "        result += vocab_reversed[predicted_id] + ' '\n",
    "        if vocab_reversed[predicted_id] == '<STOP>':\n",
    "            # 去掉结尾的空格\n",
    "            return result.strip(), sentence, attention_plot\n",
    "\n",
    "        # the predicted ID is fed back into the model\n",
    "        dec_input = tf.expand_dims([predicted_id], 0)\n",
    "\n",
    "    return result, sentence, attention_plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "# function for plotting the attention weights\n",
    "def plot_attention(attention, sentence, predicted_sentence):\n",
    "    fig = plt.figure(figsize=(10,10))\n",
    "    ax = fig.add_subplot(1, 1, 1)\n",
    "    ax.matshow(attention, cmap='viridis')\n",
    "\n",
    "    fontdict = {'fontsize': 14}\n",
    "\n",
    "    ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)\n",
    "    ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)\n",
    "\n",
    "    ax.xaxis.set_major_locator(ticker.MultipleLocator(1))\n",
    "    ax.yaxis.set_major_locator(ticker.MultipleLocator(1))\n",
    "\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "def translate(sentence):\n",
    "    result, sentence, attention_plot = evaluate(sentence)\n",
    "    sentence = pp.sentence_proc(sentence)\n",
    "    print('Input: %s' % (sentence))\n",
    "    print('Predicted translation: {}'.format(result))\n",
    "\n",
    "    attention_plot = attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]\n",
    "    plot_attention(attention_plot, sentence.split(' '), result.split(' '))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input: 奔驰 方向机 重 助力 泵 方向机 都 换\n",
      "Predicted translation: 除味 迹象 驰加 高 热心 更胜一筹 迹象 驰加 高 热心 更胜一筹 迹象 驰加 高 热心 更胜一筹 迹象 驰加 高 热心 更胜一筹 迹象 驰加 高 热心 更胜一筹 迹象 驰加 高 热心 更胜一筹 迹象 驰加 \n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x720 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sentence='奔驰的方向机重，助力泵，方向机都换了还是一样'\n",
    "translate(sentence)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:tf2.0]",
   "language": "python",
   "name": "conda-env-tf2.0-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "307.2px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
